#ifndef DW_GRAPHICS_OPENGL_BUFFER_OBJECT_INL
#	define DW_GRAPHICS_OPENGL_BUFFER_OBJECT_INL

namespace dw {
	namespace graphics {
		namespace details {

inline void BufferObject::resize(uint32 elementsCount)
{
	dwAssert(isValid(), "Invalid call to resize: buffer is not valid");
	dwAssert(isBound(), "Invalid call to resize: buffer is not bound");

	m_size = m_elementSize * elementsCount;

	glBufferData(m_target, m_size, NULL, m_usage);
}

inline void BufferObject::bind()
{
	dwAssert(m_id != 0 && m_elementSize != 0, "Invalid call to bind: buffer is not valid");
	//! @todo : dwAssert(!isValid() || !isBound(), "Invalid call to bind: buffer is already bound"); performance warning when binding same buffer

	if (!isBound())
	{
		glBindBuffer(m_target, m_id);
	}
}

inline void BufferObject::unbind()
{
	dwAssert(isValid(), "Invalid call to unbind: buffer is not valid");
	dwAssert(isBound(), "Invalid call to unbind: buffer is not bound");

	glBindBuffer(m_target, 0);
}

inline void* BufferObject::map(accesses access, uint32 elementOffset /*= 0*/)
{
	dwAssert(isValid(), "Invalid call to map: buffer is not valid");
	dwAssert(isBound(), "Invalid call to map: buffer is not bound");
	dwAssert(!isMapped(), "Invalid call to map: buffer is already mapped");
	dwAssert(elementOffset <= getCount(), "Invalid parameter: elementOffset is out of bounds");

	return reinterpret_cast< byte* >(glMapBuffer(m_target, glAccess(access))) + (elementOffset * m_elementSize);
}

inline void BufferObject::unmap()
{
	dwAssert(isValid(), "Invalid call to unmap: buffer is not valid");
	dwAssert(isBound(), "Invalid call to unmap: buffer is not bound");
	dwAssert(isMapped(), "Invalid call to unmap: buffer is not mapped");

	GLboolean result = glUnmapBuffer(m_target);

	dwAssert(result == GL_TRUE, "An error occured while unmaping the buffer");
}

inline void BufferObject::read(void* data) const
{
	dwAssert(isValid(), "Invalid call to read: buffer is not valid");
	dwAssert(isBound(), "Invalid call to read: buffer is not bound");
	dwAssert(data != NULL, "Invalid parameter: data is null");

	glGetBufferSubData(m_target, 0, m_size, data);
}

inline void BufferObject::read(void* data, uint32 elementOffset, uint32 elementsCount) const
{
	dwAssert(isValid(), "Invalid call to read: buffer is not valid");
	dwAssert(isBound(), "Invalid call to read: buffer is not bound");
	dwAssert(data != NULL, "Invalid parameter: data is null");
	dwAssert((elementOffset + elementsCount) <= getCount(), "Invalid parameter: elementOffset and elementsCount are out of bounds");

	glGetBufferSubData(m_target, elementOffset * m_elementSize, elementsCount * m_elementSize, data);
}

inline void BufferObject::write(const void* data)
{
	dwAssert(isValid(), "Invalid call to write: buffer is not valid");
	dwAssert(isBound(), "Invalid call to write: buffer is not bound");
	dwAssert(!isMapped(), "Invalid call to write: buffer is mapped");
	dwAssert(data != NULL, "Invalid parameter: data is null");

	glBufferSubData(m_target, 0, m_size, data);
}

inline void BufferObject::write(const void* data, uint32 elementOffset, uint32 elementsCount)
{
	dwAssert(isValid(), "Invalid call to write: buffer is not valid");
	dwAssert(isBound(), "Invalid call to write: buffer is not bound");
	dwAssert(!isMapped(), "Invalid call to write: buffer is mapped");
	dwAssert(data != NULL, "Invalid parameter: data is null");
	dwAssert((elementOffset + elementsCount) <= getCount(), "Invalid parameter: elementOffset and elementsCount are out of bounds");

	glBufferSubData(m_target, elementOffset * m_elementSize, elementsCount * m_elementSize, data);
}

inline bool BufferObject::isValid() const
{
	return m_id != 0 && m_elementSize != 0 && glIsBuffer(m_id) == GL_TRUE;
}

inline bool BufferObject::isBound() const
{
	GLint params = 0;

	glGetIntegerv(m_binding, &params);

	return params == m_id;
}

inline bool BufferObject::isMapped() const
{
	GLint params = 0;

	glGetBufferParameteriv(m_target, GL_BUFFER_MAPPED, &params);

	return params == GL_TRUE;
}

inline uint32 BufferObject::getSize() const
{
	return m_size;
}

inline uint32 BufferObject::getCount() const
{
	dwAssert(isValid(), "Invalid call to getCount: buffer is not valid");

	return m_size / m_elementSize;
}

inline uint32 BufferObject::getElementSize() const
{
	return m_elementSize;
}

inline BufferObject::BufferObject(types type, updates update, usages usage)
:	super(0)
,	m_target(glTarget(type))
,	m_binding(glBinding(type))
,	m_usage(glUsage(update, usage))
,	m_managed(false)
,	m_id(0)
,	m_elementSize(0)
{
	glGenBuffers(1, &m_id);
}

inline BufferObject::BufferObject(GLuint id, types type, updates update, usages usage)
:	super(0)
,	m_target(glTarget(type))
,	m_binding(glBinding(type))
,	m_usage(glUsage(update, usage))
,	m_managed(true)
,	m_id(id)
,	m_elementSize(0)
{
}

inline BufferObject::~BufferObject()
{
	if (!m_managed)
	{
		glDeleteBuffers(1, &m_id);
	}
}

inline void BufferObject::setElementSize(uint32 size)
{
	m_elementSize = size;
}

/*static*/ inline GLenum BufferObject::glTarget(types type)
{
	return glTargets[type];
}

/*static*/ inline GLenum BufferObject::glBinding(types type)
{
	return glBindings[type];
}

/*static*/ inline GLenum BufferObject::glAccess(accesses access)
{
	return glAccesses[access];
}

/*static*/ inline GLenum BufferObject::glUsage(updates update, usages usage)
{
	return glUsages[(update << 2) | usage];
}

		} // namespace details
	} // namespace graphics
} // namespace dw

#endif // !DW_GRAPHICS_OPENGL_BUFFER_OBJECT_INL
